package com.gitee.wsl.mathematics.geometry.d2.path

import com.gitee.wsl.mathematics.geometry.d2.contour.box.Box2
import com.gitee.wsl.mathematics.geometry.d2.curve.CurveShape2d
import com.gitee.wsl.mathematics.geometry.d2.curve.CurveLine
import com.gitee.wsl.mathematics.vector.ext.approx
import com.gitee.wsl.mathematics.vector.vec2.Vec2f


data class Path2(
    private val curves: Array<CurveShape2d>,
    private val bounds: Box2,
    val isRing: Boolean
) {
    fun reverse() = Path2(curves.map {
        it.reverse()
    }.reversed())

    fun curves(): Array<CurveShape2d> = curves

    fun bounds(): Box2 = bounds

    fun vertices(error: Double): Iterable<Vec2f> {
        val result = mutableListOf<Vec2f>()
        for (c in curves) {
            val segments = c.subdivide(error)
            if (result.isEmpty()) {
                result.addAll(segments)
            } else {
                val t1 = (result[result.size - 1]-result[result.size - 2]).normalized
                val t2 = (segments[1]-segments[0]).normalized
                if (t1 approx t2) {
                    result.removeAt(result.size - 1)
                }
                for (i in 1 until segments.size) {
                    result.add(segments[i])
                }
            }
        }
        return result
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as Path2

        if (!curves.contentEquals(other.curves)) return false
        if (bounds != other.bounds) return false
        if (isRing != other.isRing) return false

        return true
    }

    override fun hashCode(): Int {
        var result = curves.contentHashCode()
        result = 31 * result + bounds.hashCode()
        result = 31 * result + isRing.hashCode()
        return result
    }

    companion object {
        fun of(vararg curves: CurveShape2d) = Path2(curves.toList())

        fun linear(vararg vertices: Vec2f): Path2 {
            val segments = mutableListOf<CurveShape2d>()
            for (i in 0 until vertices.size - 1) {
                val a = vertices[i]
                val b = vertices[i + 1]
                if (!(a approx b)) {
                    segments.add(CurveLine.line(vertices[i], vertices[i + 1]))
                }
            }
            return Path2(segments)
        }
    }
}


fun Path2(cs: Iterable<CurveShape2d>): Path2 {
    val l = ArrayDeque<CurveShape2d>()
    var bounds = Box2.EMPTY
    for (a in cs) {
        for (b in a.split(a.inflections())) {
            l.addLast(b)
            bounds = bounds.union(b.start()).union(b.end())
        }
    }
    val isRing = l.first().start() approx  l.last().end()
    val curves = l.toTypedArray()
    for (i in 0 until curves.size - 1) {
        curves[i] = curves[i].endpoints(curves[i].start(), curves[i + 1].start())
    }
    if (isRing) {
        val lastIdx = curves.size - 1
        curves[lastIdx] = curves[lastIdx].endpoints(curves[lastIdx].start(), curves[0].start())
    }

    return Path2(curves, bounds, isRing)
}